home *** CD-ROM | disk | FTP | other *** search
- // **********************************************
- // File: MIDI.CPP
- // MIDI file creation functions
-
- #include "muzika.h"
- #include <alloc.h>
- #include <dir.h>
- #include <fstream.h>
- #include <io.h>
- #include <string.h>
- #include <values.h>
-
- // **********************************************
- // MIDIEvent is a class type for event lists
- // used in MIDISecondPass.
-
- class MIDIEvent : public Object {
- public:
- unsigned long delta;
- unsigned char freq;
- enum EVENT {PAUSE, NOTEOFF} code;
-
- MIDIEvent() {}
- MIDIEvent(unsigned long d, unsigned char f, enum EVENT c)
- {delta = d; freq = f; code = c;}
- virtual classType isA() const {return 0;}
- virtual char *nameOf() const {return NULL;}
- virtual hashValueType hashValue() const {return 0;}
- virtual int isEqual(const Object &) const {return 0;}
- virtual void printOn(ostream &) const {}
- };
-
- // **********************************************
- // MIDIStaff converts a multiple staff to a temporary
- // MIDI-like file during the first pass of the translation.
- // The temporary file contains information about note-on
- // and note-off events, to be converted to a final format
- // during the second pass.
-
- void MIDIStaff(ostream &out, int baseStaff, int multiplicity)
- {
- // Allocate a few lists for use in the sequel
- int *duration = (int *) malloc(multiplicity*sizeof(int));
- int *index = (int *) malloc(multiplicity*sizeof(int));
- BOOL *staffEnd = (int *) malloc(multiplicity*sizeof(BOOL));
- memset(duration, 0, multiplicity*sizeof(int));
- memset(index, 0, multiplicity*sizeof(int));
- memset(staffEnd, 0, multiplicity*sizeof(BOOL));
- BOOL multipleEnd;
-
- // Loop as long as the end has not been reached on all single staves
- do {
- // Find the minimum next object duration among the staves
- int minIndex = FindMinDuration(duration, multiplicity);
- int minDuration = duration[minIndex];
- int partIndex = 0, staffIndex = 0, anIndex = 0;
- multipleEnd = TRUE;
-
- // Subtract the minimum duration from all staves
- while (partIndex < melody.part.number()) {
- Part &p = *((Part *) &melody.part[partIndex]);
- Staff &s = *((Staff *) &p.staff[baseStaff*p.multiplicity()+staffIndex]);
-
- if (!(duration[anIndex] -= minDuration)) {
- // The duration has reached zero as a result of the subtraction:
- // place the next object in the temporary event file
- int currX = ((PointObject *) &s.pointObject[index[anIndex]])->X();
- while (!(staffEnd[anIndex] =
- index[anIndex] == s.pointObject.number())) {
- PointObject &obj =
- *((PointObject *) &s.pointObject[index[anIndex]]);
-
- if (obj.X() == currX) {
- if (duration[anIndex] < obj.Duration())
- duration[anIndex] = obj.Duration();
-
- // Use the object's MIDIPlay virtual function to write
- // the object information to the file
- obj.MIDIPlay(out, partIndex);
- ++index[anIndex];
- }
- else break;
- }
- }
-
- // Check whether the end has been reached on all single staves
- multipleEnd = multipleEnd && staffEnd[anIndex];
- ++anIndex;
- ++staffIndex;
- if (staffIndex >= p.staff.number()) {
- ++partIndex;
- staffIndex = 0;
- }
- }
- } while (!multipleEnd);
-
- // Free the temporary lists on the heap
- free(staffEnd);
- free(index);
- free(duration);
- }
-
- // **********************************************
- // MIDIFirstPass performs the first pass of the
- // melody-to-MIDI translation process, creating a temporary
- // MIDI-like file with information about the played notes.
-
- void MIDIFirstPass(ofstream &out)
- {
- // Calculate the total number of staves
- int scoreMultiplicity = 0;
- scoreStaves = 0;
- for (int index = 0; index < melody.part.number(); ++index) {
- Part &p = *((Part *) &melody.part[index]);
- scoreMultiplicity += p.multiplicity();
- if (p.staff.number()/p.multiplicity() > scoreStaves)
- scoreStaves = p.staff.number()/p.multiplicity();
- }
-
- // Call MIDIStaff once per every multiple staff
- for (int scoreIndex = 0; scoreIndex < scoreStaves; ++scoreIndex)
- MIDIStaff(out, scoreIndex, scoreMultiplicity);
- }
-
- // **********************************************
- // InsertEvent inserts a MIDI event in an IndexedList object
- // during the second pass of the translation. The events
- // are held in a delta list, with the delta field holding the
- // times between events.
-
- void InsertEvent(IndexedList &list, enum MIDIEvent::EVENT event,
- unsigned char freq, unsigned long delay)
- {
- MIDIEvent *e;
-
- // Find the index to insert the event at, reducing the delay in the way
- for (int i = 0;
- i < list.number() && (e = ((MIDIEvent *) &list[i]))->delta < delay;
- ++i)
- delay -= e->delta;
-
- // Subtract the new event's delay from the next event
- if (i < list.number())
- e->delta -= delay;
-
- // Insert the new event in the list
- list.insertAt(*new MIDIEvent(delay, freq, event), i);
- }
-
- // **********************************************
- // WriteVariableLength converts a long-integer duration
- // to the variable-length, MIDI-file format.
-
- int WriteVariableLength(ostream &out, unsigned long duration)
- {
- out.put((char) duration);
- return 1;
- }
-
- // **********************************************
- // MIDISecondPass converts the temporary file, created by
- // MIDIFirstPass, to the final-version MIDI file. The main job
- // of MIDISecondPass is to insert note-off events at the right places.
- // Whenever a note-on event is encountered, a note-off event is
- // inserted in the appropriate event list, and is eventually
- // written to the MIDI file.
-
- void MIDISecondPass(ifstream &in, ostream &out)
- {
- const int n = melody.part.number();
- IndexedList event[16];
- unsigned long length = 0;
- unsigned char code;
- unsigned long duration;
- BOOL allEmpty = TRUE, oneEmpty;
-
- // Write the MIDI file header
- out.write("MThd\0\0\0\6\0\0\0\1\0\x0C", 14);
- out.write("MTrk\0\0\0\0", 8);
- out.write("\0\xFF\x58\4\4\2\x18\8", 8);
- out.write("\0\xFF\x51\3\8\x2C\xA2", 7);
- length += 15;
- for (int index = 0; index < n; ++index) {
- out.put(0);
- out.put(0xC0+index);
- out.write("\x58\5", 2);
- length += 4;
- }
-
- // Loop as long as the file and the event lists are not over with
- while (!in.eof() || !allEmpty) {
- // Check if there are empty lists
- oneEmpty = FALSE;
- for (index = 0; index < n; ++index)
- oneEmpty = oneEmpty || (event[index].number() == 0);
-
- // If at least one empty list, try to read another event from the file
- if (oneEmpty && !in.eof()) {
- if ((code = in.get()) >= 0x80 && code <= 0x8F) {
- // The code is a Pause of some kind:
- // Put a PAUSE event at end of list
- in.read((char *) &duration, sizeof(int));
- InsertEvent(event[code-0x80], MIDIEvent::PAUSE, 0, duration);
- }
- else if (code >= 0x90 && code <= 0x9F) {
- // The code is a Note of some kind:
- // Put a "note on" event in file
- // and insert a NOTEOFF event at end of list
- in.read((char *) &duration, sizeof(int));
- out.put(0);
- out.put(code);
- unsigned char freq;
- out.put(freq = in.get());
- out.put(in.get());
- length += 4;
- InsertEvent(event[code-0x90], MIDIEvent::NOTEOFF, freq, duration);
- }
- else if (code == 0xFF)
- // Code is a meta-event:
- // do whatever is supported about meta-events (currently nothing)
- ;
- }
- else {
- // No empty lists:
- // Find the list with minimum delta-time
- // in order to write its event into the MIDI file
- unsigned long minDelta = MAXLONG;
- for (index = 0; index < n; ++index)
- if (event[index].number())
- if (((MIDIEvent *) &event[index][0])->delta < minDelta)
- minDelta = ((MIDIEvent *) &event[index][0])->delta;
- duration = minDelta;
-
- // Subtract the delta-time from all lists
- for (index = 0; index < n; ++index)
- if (event[index].number())
- if (!(((MIDIEvent *) &event[index][0])->delta -= minDelta)) {
- // Output the event to file and accumulate length
- MIDIEvent &e = *((MIDIEvent *) &event[index][0]);
- length += WriteVariableLength(out, duration)+3;
- out.put(0x80+index);
- out.put(e.freq);
- out.put(0x7F);
- duration = 0;
- event[index].destroyAt(0);
- }
- }
-
- // See if all lists have been processed
- allEmpty = TRUE;
- for (index = 0; index < n; ++index)
- allEmpty = allEmpty && (event[index].number() == 0);
- }
-
- // Write a Track End event
- out.write("\0\xFF\x2F\0", 4);
-
- // Update the track length information
- length += 4;
- out.seekp(18);
- char revlength[sizeof(length)];
- memcpy(revlength, &length, sizeof(length));
- for (index = sizeof(length)-1; index >= 0; --index)
- out.put(revlength[index]);
- }
-
- // **********************************************
- // CreateMIDIFile is called from the outside to create a MIDI file.
- // It calls MIDIFirstPass with a temporary file and then
- // MIDISecondPass to translate the temporary to a final file.
-
- void CreateMIDIFile(ostream &out)
- {
- char path[MAXPATH];
- strcpy(path, ".\\");
- ofstream temp(creattemp(path, 0));
- MIDIFirstPass(temp);
- temp.close();
- ifstream temp1(path, ios::binary | ios::in);
- MIDISecondPass(temp1, out);
- temp1.close();
- unlink(path);
- }
-